﻿using XAMSDK.Types;
using System.Collections.Generic;

namespace XAMSDK
{
    /// <summary>
    /// Abstract base class for all types of Job that may be run on an XSytem. Derived classes represent "real" job types.
    /// </summary>
    public abstract class XJob
    {
        protected XSet xset = null;

        #region Properties
        /// <summary>
        /// Is the XJob in a Healthy state?
        /// </summary>
        public bool Healthy
        {
            get
            {
                XString health = new XString();

                xset.GetField(Constants.JOB_ERRORHEALTH, health);
                return health == Constants.JOB_ERRORHEALTH_OK;
            }
        }

        /// <summary>
        /// Is the XJob complete?
        /// </summary>
        public bool Complete
        {
            get
            {
                return Status == Constants.JOB_STATUS_COMPLETE;
            }
        }

        /// <summary>
        /// The Status string asscociated with the XJob
        /// </summary>
        protected XString Status
        {
            get
            {
                XString status = new XString();

                xset.GetField(Constants.JOB_STATUS, status);
                return status;
            }
        }

        /// <summary>
        /// The Error string associated with the XJob.
        /// </summary>
        protected XString Error
        {
            get
            {
                XString error = new XString();

                xset.GetField(Constants.JOB_ERROR, error);
                return error;
            }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Submit the XJob to the XSystem for processing
        /// </summary>
        public virtual void Submit()
        {
            int retval = Imports.XSet_SubmitJob(xset.Handle);
            Helpers.CheckAndThrow(retval, "XSet_SubmitJob");
        }

        /// <summary>
        /// Halt the processing of the XJob associated with this instance.
        /// </summary>
        public virtual void Halt()
        {
            int retval = Imports.XSet_HaltJob(xset.Handle);
            Helpers.CheckAndThrow(retval, "XSet_HaltJob");
        }

        /// <summary>
        /// Process the results of the XJob. Derived classes should override this to provide XJob specific processing.
        /// </summary>
        /// <returns></returns>
        public abstract bool ProcessResults();
        #endregion
    }

    public class XQuery: XJob
    {
        protected XString level = new XString();
        byte[] query;
        protected XStream resultStream = null;
        List<XUID> resultList = new List<XUID>();

        #region Properties
        public List<XUID> ResultList { get { return resultList; } }

        public XString Query
        {
            get
            {
                return Helpers.encoder.GetString(query);
            }
        }

        public XString Level
        {
            get
            {
                return level;
            }
        }

        public long ResultCount
        {
            get
            {
               long resultCount = 0;

               if (xset.ContainsField(Constants.JOB_QUERY_RESULTS_COUNT))
                    xset.GetField(Constants.JOB_QUERY_RESULTS_COUNT, out resultCount);

                return resultCount;
            }
        }
        #endregion
        #region Constructors
        /// <summary>
        /// Internal constructor. Called by the XSystem factory method.
        /// </summary>
        /// <param name="xsys">the XSystem that the XQuery wiull run on</param>
        /// <param name="xs">the XSet that contains the XQuery details and results</param>
        /// <param name="queryStr">the Query to be executed, must be XAM QL compliant</param>
        /// <param name="binding">the binding status of the Query command field</param>
        internal XQuery(XSystemHandle xsys, XSet xs, XString queryStr, bool binding)
        {
            XStream command = null;
            xset = xs;
            xset.CreateField(Constants.JOB_COMMAND, Constants.JOB_QUERY, binding);

            query = Helpers.encoder.GetBytes("select \".xset.xuid\" " + queryStr);

            command = xset.CreateXStream(Constants.JOB_QUERY_COMMAND, Constants.TEXT_PLAIN_MIME_TYPE);
            command.Write(query, 0, (int) query.Length);
            command.Close();
        }

        /// <summary>
        /// Convenience constructor which creates the XQuery with a new XSet
        /// </summary>
        /// <param name="xsys">the XSystem that the XQuery wiull run on</param>
        /// <param name="q">the Query to be executed, must be XAM QL compliant</param>
        /// <param name="binding">the binding status of the Query command field</param>
        public XQuery(XSystem xsys, XString q, bool binding)
            :this(xsys.handle, xsys.CreateXSet(), q, binding)
        {
        }

        /// <summary>
        /// Convenience constructor which creates the XQuery with a new XSet and binding commnand field.
        /// </summary>
        /// <param name="xsys">the XSystem that the XQuery wiull run on</param>
        /// <param name="q">the Query to be executed, must be XAM QL compliant</param>
        public XQuery(XSystem xsys, XString q)
            : this(xsys.handle, xsys.CreateXSet(), q, true)
        {
        }
        #endregion

        /// <summary>
        /// Process the results of the XQuery. This can be called multiple times while the job is running.
        /// The resukts stream is opened (if necessary) and the XUID XStrem is read. Individual XUID objects
        /// are then created and added to the ResultsList for application specific processing.
        /// </summary>
        /// <returns></returns>
        public override bool ProcessResults()
        {
            byte[] xuid = new byte[Constants.XUID_SZ];
            int bytesRead = 0;

            if (!Healthy)
            {
                return false;
            }

            if (ResultCount > 0)
            {
                if (xset.ContainsField(Constants.JOB_QUERY_RESULTS))
                {
                    if (resultStream == null)
                        resultStream = xset.OpenXStream(Constants.JOB_QUERY_RESULTS, Constants.XSTREAM_MODE_READ_ONLY);
                    
                    do
                    {
                        // Try and read as much of the stream as possible as this is more efficient than doing
                        // a single read for each XUID
                        long xuidsToAdd = (resultStream.Length - resultStream.Tell) / Constants.XUID_SZ;
                        long xuidBufferSz = xuidsToAdd * Constants.XUID_SZ;
                        byte[] buffer = new byte[xuidBufferSz];

                        bytesRead = resultStream.Read(buffer, 0, (int)xuidBufferSz);

                        // We did not get all the bytes we asked for
                        if (bytesRead != xuidBufferSz)
                        {
                            // Work out how many XUIDS we did get and from this the amount of valid data in the buffer
                            xuidsToAdd = bytesRead / Constants.XUID_SZ;
                            xuidBufferSz = xuidsToAdd * Constants.XUID_SZ;

                            // If we are not exactly on a XUID boundary we need to reposition
                            if (xuidBufferSz != bytesRead)
                                resultStream.Seek(xuidBufferSz - bytesRead, System.IO.SeekOrigin.Current);
                        }

                        for (bytesRead = 0; bytesRead < xuidBufferSz; bytesRead += Constants.XUID_SZ)
                        {
                            System.Array.Copy(buffer, bytesRead, xuid, 0, Constants.XUID_SZ);

                            // Add the XUID to the collection
                            resultList.Add(new XUID(xuid));
                        }

                    } while (Complete
                             && (bytesRead > 0)
                             && (resultStream.Position != (ResultCount * Constants.XUID_SZ))); // If the Query has finished make sure we process all of the results

                }
            }

            return true;
        }

        /// <summary>
        /// Submits the XJob associated with this instance to the XSystem and sets the asscoiated Query Level.
        /// </summary>
        public override void Submit()
        {
            base.Submit();
            xset.GetField(Constants.JOB_QUERY_LEVEL, level);
        }

        /// <summary>
        /// Creates a string representation of the XQuery from its members.
        /// </summary>
        /// <returns>string representation</returns>
        public override string ToString()
        {
            return "Query submitted: " + Query + " Level " + Level + " ResultCount " + ResultCount + " Status " + Status + " Error " + Error;
        }
    }
}